﻿using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;

namespace Obi{

    [CreateAssetMenu(fileName = "distance field", menuName = "Obi/Distance Field", order = 181)]
	[ExecuteInEditMode]	
	public class ObiDistanceField : ScriptableObject
	{
		[SerializeProperty("InputMesh")]
		[SerializeField] private Mesh input = null;

		[HideInInspector][SerializeField] private float minNodeSize = 0;
		[HideInInspector][SerializeField] private Bounds bounds = new Bounds(); 
		[HideInInspector] public List<DFNode> nodes;		/**< list of distance field nodes*/

		[Range(0.0000001f,0.1f)]
		public float maxError = 0.01f;

		[Range(1, 8)]
		public int maxDepth = 5;

		public bool Initialized{
			get{return nodes != null;}
		}

		public Bounds FieldBounds {
			get{return bounds;}
		}

		public float EffectiveSampleSize {
			get{return minNodeSize;}
		}

		public Mesh InputMesh{
			set{
				if (value != input){
					Reset();
					input = value;
				}
			}
			get{return input;}
		}

		public void Reset(){
			nodes = null;
			if (input != null)
				bounds = input.bounds;
		}

		public IEnumerator Generate(){

			Reset();

			if (input == null)
				yield break;

            int[] tris = input.triangles;
            Vector3[] verts = input.vertices;

            nodes = new List<DFNode>();
            var buildingCoroutine = ASDF.Build(maxError, maxDepth, verts, tris, nodes);

            while (buildingCoroutine.MoveNext())
                yield return new CoroutineJob.ProgressInfo("Processed nodes: " + nodes.Count, 1);

            // calculate min node size;
            minNodeSize = float.PositiveInfinity;
            for (int j = 0; j < nodes.Count; ++j)
                minNodeSize = Mathf.Min(minNodeSize, nodes[j].center[3] * 2);

            // get bounds:
            float max = Mathf.Max(bounds.size[0], Mathf.Max(bounds.size[1], bounds.size[2])) + 0.2f;
            bounds.size = new Vector3(max, max, max);

        }

		/**
		 * Return a volume texture containing a representation of this distance field.
		 */
		public Texture3D GetVolumeTexture(int size){

			if (!Initialized)
				return null;
	
			// upper bound of the distance from any point inside the bounds to the surface.
			float maxDist = Mathf.Max(bounds.size.x,bounds.size.y,bounds.size.z);				
	
			float spacingX = bounds.size.x / (float)size;
			float spacingY = bounds.size.y / (float)size;
			float spacingZ = bounds.size.z / (float)size;
	
			Texture3D tex = new Texture3D (size, size, size, TextureFormat.Alpha8, false);
	
			var cols = new Color[size*size*size];
			int idx = 0;
			Color c = Color.black;
	
			for (int z = 0; z < size; ++z)
			{
				for (int y = 0; y < size; ++y)
				{
					for (int x = 0; x < size; ++x, ++idx)
					{
						Vector3 samplePoint = bounds.min + new Vector3(spacingX * x + spacingX*0.5f,
						                                 			   spacingY * y + spacingY*0.5f,
						                                  			   spacingZ * z + spacingZ*0.5f);

                        float distance = ASDF.Sample(nodes,samplePoint);
	
						if (distance >= 0)
							c.a = distance.Remap(0,maxDist*0.1f,0.5f,1);
						else 
							c.a = distance.Remap(-maxDist*0.1f,0,0,0.5f);
	
						cols[idx] = c;
					}
				}
			}
			tex.SetPixels (cols);
			tex.Apply ();
			return tex;
	
		}
	}
}

